﻿using Levfier.Proccessors.Measuring;
using Levfier.Proccessors.Parameters;
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Levfier.GUI.EditorControls.Measuring
{
    /// <summary>
    /// スペクトルのビットマップを描画するクラス
    /// </summary>
    public class SpectrumBitmapDrawer : IBitmapDrawable
    {
        #region 定数

        /// <summary>
        /// ログモード時の周波数の下限[Hz]
        /// </summary>
        private static readonly double LogModeFreqLowLimit = 50;

        /// <summary>
        /// ログモード時の周波数の上限[Hz]
        /// </summary>
        private static readonly double LogModeFreqHighLimit = 12000;

        /// <summary>
        /// 周波数の上限[Hz]
        /// </summary>
        private static readonly double NormalModeFreqHighLimit = 10000;

        /// <summary>
        /// ビットマップ幅のデフォルト値
        /// </summary>
        private static readonly int DefaultWidth = 320;

        /// <summary>
        /// ビットマップ高さのデフォルト値
        /// </summary>
        private static readonly int DefaultHeight = 320;

        /// <summary>
        /// デシベル表示時のスームレート
        /// </summary>
        private static readonly double DbZoomRate = 2.0d;

        /// <summary>
        /// 周波数のメモリ LOG
        /// </summary>
        private static readonly int[] FreqScaleLog = 
        {
            63,125,250,500,1000,2000,4000,8000,16000
        };

        /// <summary>
        /// 周波数のメモリ normal
        /// </summary>
        private static readonly int[] FreqScale = 
        {
            0,1000,2000,3000,4000,5000,6000,7000,8000,9000,10000,11000,12000,13000,14000,15000,16000
        };

        private static readonly double ZoomGain = 300;

        #endregion

        /// <summary>
        /// データソース
        /// </summary>
        private SpectrumAnalyzer analyzer;

        /// <summary>
        /// 描画用オブジェクト
        /// </summary>
        private RenderTargetBitmap bitmap;

        /// <summary>
        /// 描画色
        /// </summary>
        private System.Windows.Media.Brush brush;

        /// <summary>
        /// 拡大倍率
        /// </summary>
        private double zoomValue;

        /// <summary>
        /// 拡大倍率の前値
        /// </summary>
        private double preZoomValue;

        /// <summary>
        /// 画像のX軸ズレ
        /// </summary>
        private double xAxisOffset;

        /// <summary>
        /// ゼロ点
        /// </summary>
        private int zeroAxis;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="analyzer">スペアナのプロセッサ</param>
        public SpectrumBitmapDrawer(SpectrumAnalyzer analyzer)
        {
            this.analyzer = analyzer;
            this.brush = new SolidColorBrush(Color.FromArgb(0xFF, 0x00, 0xAA, 0xDE));
            this.preZoomValue = 1.0d;
            this.zoomValue = 1;
            this.zeroAxis = 0;
        }

        /// <summary>
        /// ビットマップを初期生成する
        /// </summary>
        /// <returns>ビットマップ</returns>
        public RenderTargetBitmap CreateBitmap()
        {
            this.bitmap = new RenderTargetBitmap(DefaultWidth, DefaultHeight, 96, 96, PixelFormats.Default);
            return this.bitmap;
        }

        /// <summary>
        /// 画面の描画を更新する
        /// </summary>
        /// <returns></returns>
        public bool Update(int freqMode, int powerMode)
        {
            //画面クリア
            this.bitmap.Clear();

            //スペクトラム強度の描画
            this.DrawSpectrumPower(freqMode, powerMode);

            //目盛の描画
            this.DrawScale(freqMode);

            return true;
        }

        /// <summary>
        /// ビットマップを拡大する
        /// </summary>
        /// <param name="p">変化量</param>
        public void Zoom(int p, int x)
        {
            this.preZoomValue = zoomValue;
            this.zoomValue *= Math.Pow(2.0d, (double)p / ZoomGain);
            this.zeroAxis = x;
            this.xAxisOffset -= (this.zoomValue / this.preZoomValue - 1.0d) * (double)(this.zeroAxis - this.xAxisOffset);
        }

        /// <summary>
        /// ビットマップを移動する
        /// </summary>
        /// <param name="distance">移動量</param>
        public void Move(int distance)
        {
            this.xAxisOffset += distance;
        }

        /// <summary>
        /// パワースペクトルを描画する
        /// </summary>
        /// <param name="freqMode"></param>
        /// <param name="powerMode"></param>
        private void DrawSpectrumPower(int freqMode, int powerMode)
        {
            double[] power = this.analyzer.PowerSpectrum;
            double[] maxPower = this.analyzer.MaxPowerSpectrum;

            Point prePoint = new Point(this.GetXAxis(5, freqMode), this.GetYAxis(0, powerMode));
            Point postPoint;
            Point maxPoint1 = new Point(this.GetXAxis(5, freqMode), this.GetYAxis(0, powerMode));
            Point maxPoint2;

            Pen pen = new Pen(this.brush, 1);
            Pen maxPen = new Pen(Brushes.Lime, 1);

            DrawingVisual drawVisual = new DrawingVisual();
            DrawingContext drawContext = drawVisual.RenderOpen();

            //チャートを描画
            for (int i = 0; i < power.Length / 2; i++)
            {
                postPoint = new Point(
                    this.GetXAxis(i * this.analyzer.FreqAccuracy, freqMode),
                    this.GetYAxis(power[i], powerMode)
                    );
                maxPoint2 = new Point(
                    this.GetXAxis(i * this.analyzer.FreqAccuracy, freqMode),
                    this.GetYAxis(maxPower[i], powerMode)
                    );

                //直近のマックス値 描画
                drawContext.DrawLine(maxPen, maxPoint1, maxPoint2);

                //現在値 描画
                drawContext.DrawLine(pen, prePoint, postPoint);

                prePoint = postPoint;
                maxPoint1 = maxPoint2;
            }
            drawContext.Close();

            this.bitmap.Render(drawVisual);
        }

        /// <summary>
        /// メモリを描画する
        /// </summary>
        private void DrawScale(int freqMode)
        {
            DrawingVisual drawVisual = new DrawingVisual();
            DrawingContext drawContext = drawVisual.RenderOpen();
            Pen pen = new Pen(Brushes.White, 1);

            int[] scale;

            if (0 == freqMode)
            {
                scale = FreqScale;
            }
            else
            {
                scale = FreqScaleLog;
            }

            for (int i = 0; i < scale.Length; i++)
            {
                double x = GetXAxis(scale[i], freqMode);
                drawContext.DrawLine(pen, new Point(x, 0), new Point(x, this.bitmap.Height));
                drawContext.DrawText(
                    new FormattedText(scale[i].ToString(),
                        CultureInfo.GetCultureInfo("en-us"),
                        FlowDirection.LeftToRight,
                        new Typeface("Meirio"),
                        12,
                        Brushes.White),
                    new Point(x - 20, this.bitmap.Height * 0.90)
                    );
            }

            drawContext.Close();

            this.bitmap.Render(drawVisual);
        }

        /// <summary>
        /// X軸座標を取得する
        /// </summary>
        /// <param name="freq">周波数[Hz]</param>
        /// <param name="mode">周波数表示モード</param>
        /// <returns>座標</returns>
        private double GetXAxis(double freq, int mode)
        {
            if (0 == mode)
            {
                return ( (freq * this.zoomValue) * (double)this.bitmap.Width / (NormalModeFreqHighLimit) ) + this.xAxisOffset;
            }
            else
            {
                return this.zoomValue * Math.Log(freq / LogModeFreqLowLimit) * (double)this.bitmap.Width / Math.Log(LogModeFreqHighLimit / LogModeFreqLowLimit) + this.xAxisOffset;
            }
        }

        /// <summary>
        /// Y軸座標を取得する
        /// </summary>
        /// <param name="power">スペクトル強度</param>
        /// <param name="mode">スペクトル強度表示モード</param>
        /// <returns>座標</returns>
        private double GetYAxis(double power, int mode)
        {
            if (0 == mode)
            {
                return -power + (this.bitmap.Height * 0.9);
            }
            else
            {
                return -ProccessParameter.ConvertValueToDB(power) * DbZoomRate + (this.bitmap.Height * 0.2);
            }
        }
    }
}
